#include "dp.h"
/*
 * get_ipc_id() 从/proc/sysvipc/文件系统中获取 IPC 的 id 号
 * pfile: 对应/proc/sysvipc/目录中的 IPC 文件分别为
 *
 msg-消息队列,sem-信号量,shm-共享内存
 * key: 对应要获取的 IPC 的 id 号的键值
 */
int dp::get_ipc_id(char *proc_file,key_t key)
{
	#define BUFSZ 256
	FILE *pf;
	int i,j;
	char line[BUFSZ],colum[BUFSZ];
	if((pf = fopen(proc_file,"r")) == NULL){
	perror("Proc file not open");
	exit(EXIT_FAILURE);
	}
	fgets(line, BUFSZ,pf);
	while(!feof(pf)){
	i = j = 0;
	fgets(line, BUFSZ,pf);
	while(line[i] == ' ') i++;
	while(line[i] !=' ') colum[j++] = line[i++];
	colum[j] = '\0';
	if(atoi(colum) != key) continue;
	j=0;
	while(line[i] == ' ') i++;
	while(line[i] !=' ') colum[j++] = line[i++];
	colum[j] = '\0';
	i = atoi(colum);
	fclose(pf);
	return i;
	}
	fclose(pf);
	return -1;
}
/*
*
set_sem 函数建立一个具有 n 个信号灯的信号量
*
如果建立成功,返回 一个信号量的标识符 sem_id
*
输入参数:
*
sem_key 信号量的键值
*
sem_val 信号量中信号灯的个数
*
sem_flag 信号量的存取权限
*/
int dp::set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
Sem_uns sem_arg;
//测试由 sem_key 标识的信号量是否已经建立
if((sem_id=get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 ){
//semget 新建一个信号灯,其标号返回到 sem_id
if((sem_id = semget(sem_key,1,sem_flg)) < 0){
perror("semaphore create error");
exit(EXIT_FAILURE);
}
}
//设置信号量的初值
sem_arg.val = sem_val;
if(semctl(sem_id,0,SETVAL,sem_arg) < 0){
perror("semaphore set error");
exit(EXIT_FAILURE);
}
return sem_id;
}
/*
*
set_shm 函数建立一个具有 n 个字节 的共享内存区
*
如果建立成功,返回 一个指向该内存区首地址的指针 shm_buf
*
输入参数:
*
shm_key 共享内存的键值
*
shm_val 共享内存字节的长度
*
shm_flag 共享内存的存取权限
*/
char * dp::set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char * shm_buf;
//测试由 shm_key 标识的共享内存区是否已经建立
if((shm_id=get_ipc_id("/proc/sysvipc/shm",shm_key))<0){
//shmget 新建 一个长度为 shm_num 字节的共享内存
if((shm_id= shmget(shm_key,shm_num,shm_flg)) <0){
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
//shmat 将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf=(char *)shmat(shm_id,0,0)) < (char *)0){
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
for(i=0; i<shm_num; i++) shm_buf[i] = 0; //初始为 0
}
//共享内存区已经建立,将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0) {
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
Sema::Sema(int id)
{
	sem_id = id;
}
Sema::~Sema(){ }
/*
 * 信号灯上的 down/up 操作
 * semid:信号灯数组标识符
 * semnum:信号灯数组下标
 * buf:操作信号灯的结构
 */
int Sema::down()
{
	struct sembuf buf;
	buf.sem_op = -1;
	buf.sem_num = 0;
	buf.sem_flg = SEM_UNDO;
	if((semop(sem_id,&buf,1)) <0) {
		perror("down error ");
		exit(EXIT_FAILURE);
	}
	return EXIT_SUCCESS;
}
int Sema::up()
{
	Sem_uns arg;
	struct sembuf buf;
	buf.sem_op = 1;
	buf.sem_num = 0;
	buf.sem_flg = SEM_UNDO;
	if((semop(sem_id,&buf,1)) <0) {
		perror("up error ");
		exit(EXIT_FAILURE);
	}
	return EXIT_SUCCESS;
}
/*
 * 用于哲学家管程的互斥执行
 */
Lock::Lock(Sema * s)
{
	sema = s;
}
Lock::~Lock(){ }
//上锁
void Lock::close_lock()
{
	sema->down();
}
//开锁
void Lock::open_lock()
{
	sema->up();
}


//用于哲学家就餐问题的条件变量
Condition::Condition(char *st[] ,Sema *sm){
	state = st;
	sema = sm;
}
/*
 * 左右邻居不在就餐,条件成立,状态变为就餐
 * 否则睡眠,等待条件成立
 */
void Condition::Wait(Lock *lock,int i)
{
	if((*state[(i+4)%5] != eating) &&(*state[i] == hungry) &&(*state[(i+1)%5] != eating))
		*state[i] = eating;//拿到筷子,进就餐态
	else{
		cout << "p" << i+1 << ":" << getpid() << " hungry\n";
		lock->open_lock();//开锁
		sema->down();//没拿到,以饥饿态等待
		lock->close_lock();//上锁
	}
}
/*
 *左右邻居不在就餐,则置其状态为就餐,
 *将其从饥俄中唤醒。否则什么也不作。
 */
void Condition::Signal(int i)
{
	if((*state[(i+4)%5] != eating) &&(*state[i] == hungry) &&(*state[(i+1)%5] != eating))
	{ //可拿到筷子,从饥饿态唤醒进就餐态
		sema->up();
		*state[i] = eating;
	}
}
//哲学家就餐问题管程构造函数
dp::dp(int r)
{
	int ipc_flg = IPC_CREAT | 0644;
	int shm_key = 220;
	int shm_num = 1;
	int sem_key = 120;
	int sem_val = 0;
	int sem_id;
	Sema *sema;
	rate = r;
	//5 个中同时可以有 2 个在就餐,建立一个初值为 2 的用于锁的信号灯
	if((sem_id = set_sem(sem_key++,2,ipc_flg)) < 0){
		perror("Semaphor create error");
		exit(EXIT_FAILURE);
	}
	sema = new Sema(sem_id);
	lock = new Lock(sema);
	for(int i=0; i<5; i++){
		//为每个哲学家建立一个条件变量和可共享的状态
		//初始状态都为思考
		if((state[i]=(char *)set_shm(shm_key++,shm_num,ipc_flg)) == NULL){
			perror("Share memory create error");
			exit(EXIT_FAILURE);
		}
		*state[i]=thinking;
		//为每个哲学家建立初值为 0 的用于条件变量的信号灯
		if((sem_id = set_sem(sem_key++,sem_val,ipc_flg)) < 0){
			perror("Semaphor create error");
			exit(EXIT_FAILURE);
		}
		sema = new Sema(sem_id);
		self[i] = new Condition(state,sema);
	}
}
//获取筷子的操作
//如果左右邻居都在就餐,则以饥俄状态阻塞
//否则可以进入就餐状态
void dp::pickup(int i){
	lock->close_lock();//进入管程,上锁
	*state[i]=hungry; //进饥饿态
	self[i]->Wait(lock,i); //测试是否能拿到两只筷子
	cout << "p" << i+1 << ":" << getpid() << " eating\n";
	sleep(rate);//拿到,吃 rate 秒
	lock->open_lock();//离开管程,开锁
}
//放下筷子的操作
//状态改变为思考,如左右邻居有阻塞者则唤醒它
void dp::putdown(int i){
	int j;
	lock->close_lock();//进入管程,上锁
	*state[i]=thinking; //进思考态
	j = (i+4)%5;
	self[j]->Signal(j);//唤醒左邻居
	j = (i+1)%5;
	self[j]->Signal(j);//唤醒右邻居
	lock->open_lock();//离开管程,开锁
	cout << "p" << i+1 << ":" << getpid() << " thinking\n";
	sleep(rate);//思考 rate 秒
}
dp::~dp(){ }
// 哲学家就餐问题并发执行的入口
int main(int argc,char *argv[])
{
	dp *tdp;
	//哲学家就餐管程对象的指针
	int pid[5]; //5 个哲学家进程的进程号
	int rate ;
	rate = (argc > 1) ? atoi(argv[1]) : 3 ;
	tdp = new dp(rate); //建立一个哲学家就餐的管程对象
	pid[0]=fork(); //建立第一个哲学家进程
	if(pid[0]<0){ perror("p1 create error"); exit(EXIT_FAILURE);}
	else if(pid[0]==0){
		//利用管程模拟第一个哲学家就餐的过程
		while(1){
			tdp->pickup(0);//拿起筷子
			tdp->putdown(0);//放下筷子
		}
	}
	pid[1]=fork();
	//建立第二个哲学家进程
	if(pid[1]<0){ perror("p2 create error"); exit(EXIT_FAILURE);}
	else if(pid[1]==0){
		//利用管程模拟第二个哲学家就餐的过程
		while(1){
			tdp->pickup(1);//拿起筷子
			tdp->putdown(1);//放下筷子
		}
	}
	pid[2]=fork(); //建立第三个哲学家进程
	if(pid[2]<0) { perror("p3 create error"); exit(EXIT_FAILURE);}
	else if(pid[2]==0){
		//利用管程模拟第三个哲学家就餐的过程
		while(1){
		tdp->pickup(2);//拿起筷子
		tdp->putdown(2);//放下筷子
		}
	}
	pid[3]=fork(); //建立第四个哲学家进程
	if(pid[3]<0) { perror("p4 create error"); exit(EXIT_FAILURE);}
	else if(pid[3]==0){
		//利用管程模拟第四个哲学家就餐的过程
		while(1){
		tdp->pickup(3);//拿起筷子
		tdp->putdown(3);//放下筷子
		}
	}
	pid[4]=fork(); //建立第五个哲学家进程
	if(pid[4]<0) { perror("p5 create error"); exit(EXIT_FAILURE);}
	else if(pid[4]==0){
		//利用管程模拟第五个哲学家就餐的过程
		while(1){
		tdp->pickup(4);//拿起筷子
		tdp->putdown(4);//放下筷子
		}
	}
return 0;
}
